use loco_rs::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use super::_entities::job_batches::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub name: String,
    pub total_jobs: i32,
    pub pending_jobs: i32,
    pub failed_jobs: i32,
    pub failed_job_ids: String,
    pub options: Option<String>,
    pub cancelled_at: Option<i32>,
    pub created_at: i32,
    pub finished_at: Option<i32>,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 1, message = "Name must not be empty."))]
    pub name: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            name: self.name.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::job_batches::ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds a job batch by the provided id
    ///
    /// # Errors
    ///
    /// When could not find job batch or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let job_batch = job_batches::Entity::find()
            .filter(
                model::query::condition()
                    .eq(job_batches::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        job_batch.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds job batches by the provided name
    ///
    /// # Errors
    ///
    /// When could not find job batches or DB query error
    pub async fn find_by_name(db: &DatabaseConnection, name: &str) -> ModelResult<Vec<Self>> {
        let job_batches = job_batches::Entity::find()
            .filter(
                model::query::condition()
                    .eq(job_batches::Column::Name, name)
                    .build(),
            )
            .all(db)
            .await?;
        Ok(job_batches)
    }

    /// Asynchronously creates a job batch and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the job batch into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let job_batch = job_batches::ActiveModel {
            name: ActiveValue::set(params.name.to_string()),
            total_jobs: ActiveValue::set(params.total_jobs),
            pending_jobs: ActiveValue::set(params.pending_jobs),
            failed_jobs: ActiveValue::set(params.failed_jobs),
            failed_job_ids: ActiveValue::set(params.failed_job_ids.to_string()),
            options: ActiveValue::set(params.options.clone()),
            cancelled_at: ActiveValue::set(params.cancelled_at),
            created_at: ActiveValue::set(params.created_at),
            finished_at: ActiveValue::set(params.finished_at),
            ..Default::default()
        }
        .insert(db)
        .await?;

        Ok(job_batch)
    }
}

impl ActiveModel {
    /// Updates the job batch's pending jobs count
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_pending_jobs(
        mut self,
        db: &DatabaseConnection,
        pending_jobs: i32,
    ) -> ModelResult<Model> {
        self.pending_jobs = ActiveValue::set(pending_jobs);
        Ok(self.update(db).await?)
    }

    /// Updates the job batch's failed jobs information
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_failed_jobs(
        mut self,
        db: &DatabaseConnection,
        failed_jobs: i32,
        failed_job_ids: String,
    ) -> ModelResult<Model> {
        self.failed_jobs = ActiveValue::set(failed_jobs);
        self.failed_job_ids = ActiveValue::set(failed_job_ids);
        Ok(self.update(db).await?)
    }

    /// Updates the job batch's cancelled status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_cancelled_status(
        mut self,
        db: &DatabaseConnection,
        cancelled_at: Option<i32>,
    ) -> ModelResult<Model> {
        self.cancelled_at = ActiveValue::set(cancelled_at);
        Ok(self.update(db).await?)
    }

    /// Updates the job batch's finished status
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_finished_status(
        mut self,
        db: &DatabaseConnection,
        finished_at: Option<i32>,
    ) -> ModelResult<Model> {
        self.finished_at = ActiveValue::set(finished_at);
        Ok(self.update(db).await?)
    }
}
